/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024.
 * Description: RTOS Process Context asm header
 * Create: 2024-07-13
 */
#include <linux/percpu-defs.h>
#include <linux/bug.h>
#include <linux/kernel.h>
#include <linux/vmalloc.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/jump_label.h>

#include "rtpc.h"

/* DFX : do_call times, fixup times, fixup type, etc */
#define STAT_SYSCNO_MAX 300
#define STAT_IRQ_MAX 200
struct rpc_stat_info {
	/* do_call times */
	size_t nr_call;
	/*
	 * fixup times
	 * nr_fixup == nr_fixup_irq + nr_fixup_syscall + nr_fixup_exception
	 */
	size_t nr_fixup;
	/*
	 * 0: irq
	 * 1: syscall
	 * 2: page fault
	 * 3: undefined instruction
	 * 4: other
	 */
	size_t nr_fixup_reason[5];
	/* fixup times by syscall: table */
	size_t syscall_table[STAT_SYSCNO_MAX + 1];
	/* fixup times by interrupt: table */
	size_t irq_table[STAT_IRQ_MAX + 1];
};

DEFINE_STATIC_KEY_FALSE(sysctl_rpc_kernel_stats);
DEFINE_PER_CPU(struct rpc_stat_info *, cpu_rpc_stat);

noinline __attribute__((cold))
void rtpc_stat_call(void)
{
	struct rpc_stat_info *const rpc_stat = __this_cpu_read(cpu_rpc_stat);

	++rpc_stat->nr_call;
}

noinline __attribute__((cold))
void rtpc_stat_fixup(const size_t reason)
{
	struct rpc_stat_info *const rpc_stat = __this_cpu_read(cpu_rpc_stat);

	++rpc_stat->nr_fixup;
#ifdef CONFIG_RTOS_RTPC_RPC_DEBUG_BUILD
	BUG_ON(reason >= ARRAY_SIZE(rpc_stat->nr_fixup_reason));
#endif
	++rpc_stat->nr_fixup_reason[reason];
}

static int rtpc_proc_show_call_stats(struct seq_file *m, void *v)
{
	int cpu;
	size_t i;

	if (!static_branch_unlikely(&sysctl_rpc_kernel_stats)) {
		seq_puts(m, "rpc stats is not enabled!\n");
		return 0;
	}

	for_each_possible_cpu(cpu) {
		struct rpc_stat_info *const rpc_stat = per_cpu(cpu_rpc_stat, cpu);

		seq_printf(m, "%d %zu %zu %zu %zu %zu %zu %zu\n", cpu, rpc_stat->nr_call,
			   rpc_stat->nr_fixup, rpc_stat->nr_fixup_reason[0],
			   rpc_stat->nr_fixup_reason[1], rpc_stat->nr_fixup_reason[2],
			   rpc_stat->nr_fixup_reason[3], rpc_stat->nr_fixup_reason[4]);
		for (i = 0; i <= STAT_SYSCNO_MAX; ++i)
			seq_printf(m, " %zu: %zu", i, rpc_stat->syscall_table[i]);
		seq_putc(m, '\n');
		for (i = 0; i <= STAT_IRQ_MAX; ++i)
			seq_printf(m, " %zu: %zu", i, rpc_stat->irq_table[i]);
		seq_putc(m, '\n');
	}
	return 0;
}

static int rtpc_call_stats_enable_show(struct seq_file *m, void *v)
{
	if (static_branch_unlikely(&sysctl_rpc_kernel_stats))
		seq_puts(m, "1\n");
	else
		seq_puts(m, "0\n");
	return 0;
}

static int rtpc_call_stats_enable_open(struct inode *inode, struct file *filp)
{
	return single_open(filp, rtpc_call_stats_enable_show, NULL);
}

static ssize_t rtpc_call_stats_enable_write(struct file *file,
		const char __user *buf, size_t count, loff_t *ppos)
{
	char kbuf[2];
	int cpu;

	if (count == 0)
		return -EINVAL;

	if (count > 2)
		count = 2;

	if (copy_from_user(kbuf, buf, count))
		return -EFAULT;

	if (kbuf[0] != '1')
		return -EINVAL;

	if (count > 1) {
		if (kbuf[1] != '\0' && kbuf[1] != '\n')
			return -EINVAL;
	}

	if (static_branch_unlikely(&sysctl_rpc_kernel_stats))
		return -EEXIST;

	for_each_possible_cpu(cpu) {
		struct rpc_stat_info *const stat_info = vzalloc(sizeof(struct rpc_stat_info));

		if (stat_info == NULL)
			goto label_failed;

		per_cpu(cpu_rpc_stat, cpu) = stat_info;
	}
	static_branch_enable(&sysctl_rpc_kernel_stats);
	return 0;

label_failed:
	for_each_possible_cpu(cpu) {
		vfree(per_cpu(cpu_rpc_stat, cpu));
		per_cpu(cpu_rpc_stat, cpu) = NULL;
	}
	return -ENOMEM;
}

static const struct proc_ops rtpc_call_stats_enable_fops = {
	.proc_open = rtpc_call_stats_enable_open,
	.proc_write = rtpc_call_stats_enable_write,
	.proc_read = seq_read,
	.proc_lseek = seq_lseek,
	.proc_release = single_release,
};

static int __init rpc_stat_init(void)
{
	/* create proc interface: /proc/rtpc_call_stats */
	if (!proc_create_single("rtpc_call_stats", 0600, NULL, rtpc_proc_show_call_stats)) {
		rpc_err("rpc proc create failed\n");
		return -1;
	}
	if (!proc_create("rtpc_call_stats_enable", 0600, NULL, &rtpc_call_stats_enable_fops)) {
		rpc_err("rpc proc create failed!\n");
		remove_proc_entry("rtpc_call_stats", NULL);
		return -1;
	}

	return 0;
}

late_initcall(rpc_stat_init);
